﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using itau.im.entidade;
using itau.im.negocio;
using EnterpriseLibraryWrapper;
using System.Threading;
using itau.im.interpolador;
using LogOnline;

namespace itau.im.gerador.pricing
{
    class GeradorPricing
    {
        #region Constantes

        private const string PARAMETRO_MAXIMOTHREADS = "MAXCURVAPROCESS";
        private const string MASCARA_DATA = "dd/MM/yyyy HH:mm:ss";
        private const string PARAMETRO_ULTIMAEXECUCAO = "DTHRULTIMAEXECUCAO";
        private const string PARAMETRO_JANELA = "JANELASEGUNDOS";
        private const string PARAMETRO_HORAS_TENTATIVA_GERACAO = "HREXPCURVAEXEC";

        private const int SITUACAO_EXECUCAO_AGENDADA = 11;
        private const int SITUACAO_EXECUCAO_SOLICITADA_UPLOAD = 14;
        private const int SITUACAO_EXECUCAO_SOLICITADA_MANUAL = 13;
        private const int SITUACAO_EXECUCAO_EM_EXECUCAO = 500;
        private const int SITUACAO_EXECUCAO_RETIRAR_AGENDAMENTO = 99;

        private const int INDICADOR_VERTICE_NAO = 0;

        private const int CURVA_TIPO_ENTRADA_COMPOSTA = 1;
        private const int CURVA_TIPO_ENTRADA_MANUAL = 2;
        private const int CURVA_TIPO_ENTRADA_IMPORTACAO_ARQUIVO = 3;

        private const int CODIGO_TIPO_CURVA_CUSTOS = 1;

        #endregion

        #region Atributos

        DateTime dataHoraFim;
        List<Item> mapaCofiguracoesGerador;
        ICurvaBS curva;
        IExecucaoCurvaBS execucaoCurva;
        IConfiguracoesBS configuracoes;
        IList<RegistroExecucaoCurva> registrosExecucaoCurva;
        IList<RegistroExecucaoCurva> registrosExecucaoCurvaPaginada;
        IList<RegistroExecucaoCurva> registrosExecucaoCurvaNaoFinalizadas;
        bool listaFoiPaginada;
        long janela;
        int maximoThreads;
        static wILog logOn = new wILogClass();
        private static Timer _timer;
        private static Timer _timerLimpezaExcecao;
        private static Timer _timerInclusaoPMT;
        private static string nomeAplicativo;

        #endregion

        #region Construtor

        public GeradorPricing(string aplicativo)
        {
            curva = new CurvaBS();
            execucaoCurva = new ExecucaoCurvaBS();
            janela = 0;
            maximoThreads = 0;
            registrosExecucaoCurva = new List<RegistroExecucaoCurva>();
            configuracoes = new ConfiguracoesBS();
            _timer = null;
            _timerLimpezaExcecao = null;
            _timerInclusaoPMT = null;
            nomeAplicativo = aplicativo;
            logOn.IniciarLog(ref nomeAplicativo);
        }

        #endregion

        #region Métodos Públicos

        public void Gerar()
        {
            try
            {
                Console.WriteLine("Gerador ...");
                GerarCurvas();
            }
            catch (Exception ex)
            {
                EnterpriseLibraryFactory.GetExceptionHandler().HandleException(ex, "ErrosGerador");
            }
            finally
            {
                LimparObjetos();
            }
        }

        #endregion

        #region Métodos Privados

        private void GerarCurvas()
        {
            CriarTimerPatrol();

            CriarTimerLimpezaExcecao();

            CriarTimerInclusaoPMT();

            ObterConfiguracoesGerador();

            ObterJanelaDeExecucaoEmSegundos();

            ObterMaximoCurvasPorPagina();

            ObterDataAtualDeExecucao();

            BuscarExecucoesNaoFinalizadas();

            ReiniciarExecucoesNaoFinalizadas();

            while (true)
            {
                try
                {
                    ObterDataAtualDeExecucao();

                    ObterCurvasAgendadas();

                    PaginarCurvas();

                    ProcessarCurvas();

                    AtualizarUltimaExecucao();

                    AguardarProximaExecucao();
                }
                catch (Exception ex)
                {
                    EnterpriseLibraryFactory.GetExceptionHandler().HandleException(ex, "ErrosGerador");
                }
            }
        }

        private void ObterMaximoCurvasPorPagina()
        {
            maximoThreads = Convert.ToInt32(mapaCofiguracoesGerador.Where(p => p.Chave == PARAMETRO_MAXIMOTHREADS).First().Valor);
            ThreadPool.SetMaxThreads(maximoThreads, maximoThreads);
        }

        private void ReiniciarExecucoesNaoFinalizadas()
        {
            foreach (RegistroExecucaoCurva registro in registrosExecucaoCurvaNaoFinalizadas)
            {
                CurvaVigente curvaVigente = curva.ObterCurvaVigentePorDataReferencia(registro.CodigoCurva, registro.DataInclusaoVigencia);

                registro.CodigoSituacao -= SITUACAO_EXECUCAO_EM_EXECUCAO;

                execucaoCurva.AtualizarRegistroExecucaoCurva(registro);

                if (registro.CodigoSituacao.Equals(SITUACAO_EXECUCAO_SOLICITADA_MANUAL))
                {
                    execucaoCurva.RemoverCurvaExecucaoPontos(registro, INDICADOR_VERTICE_NAO);
                }
                else
                {
                    execucaoCurva.RemoverCurvaExecucaoPontos(registro);
                }
            }
        }

        private void BuscarExecucoesNaoFinalizadas()
        {
            registrosExecucaoCurvaNaoFinalizadas = execucaoCurva.ObterRegistrosExecucaoCurvaPricingBIHF(dataHoraFim, SITUACAO_EXECUCAO_AGENDADA + SITUACAO_EXECUCAO_EM_EXECUCAO, SITUACAO_EXECUCAO_SOLICITADA_UPLOAD + SITUACAO_EXECUCAO_EM_EXECUCAO);
        }

        private void CriarTimerPatrol()
        {
            _timer = new Timer(new TimerCallback(GerarLogPatrol), null, 0, 3 * 60000);
        }

        private void CriarTimerLimpezaExcecao()
        {
            _timerLimpezaExcecao = new Timer(new TimerCallback(LimparCurvasEmExecucao), null, 0, 3 * 6000);
        }

        private void CriarTimerInclusaoPMT()
        {
            _timerInclusaoPMT = new Timer(new TimerCallback(IncluirExecucaoPMT), null, 0, 3 * 6000);
        }

        static void GerarLogPatrol(Object state)
        {
            logOn.TxtPatrol(ref nomeAplicativo);
        }

        static void LimparCurvasEmExecucao(Object state)
        {
            try
            {
                FilaExcecao.ProcessarLimpeza();
            }
            catch (Exception ex)
            {
                Exception exCustom = new Exception("Erro na limpeza da execucao no LimparCurvasEmExecucao", ex);
                EnterpriseLibraryFactory.GetExceptionHandler().HandleException(exCustom, "ErrosGerador");
            }
        }

        static void IncluirExecucaoPMT(Object state)
        {
            try
            {
                FilaInclusaoPMT.ProcessarInclusaoPMT();
            }
            catch (Exception ex)
            {
                EnterpriseLibraryFactory.GetExceptionHandler().HandleException(ex, "ErrosGerador");
            }
        }

        private void ObterConfiguracoesGerador()
        {
            try
            {
                mapaCofiguracoesGerador = configuracoes.ObterMapaConfiguracoesPorDominio(nomeAplicativo);
            }
            catch (Exception)
            {
                throw new Exception("Erro ao obter as configurações do Gerador.");
            }
        }

        private void ObterJanelaDeExecucaoEmSegundos()
        {
            try
            {
                janela = long.Parse(mapaCofiguracoesGerador.Where(p => p.Chave == PARAMETRO_JANELA).First().Valor);

                Logger.GravarLog(string.Format("Janela de execução: {0}", janela));
            }
            catch (Exception)
            {
                throw new Exception("O parâmetro Janela de Execução não foi cadastrado, ou está em um formato inválido.");
            }
        }

        private void ObterCurvasAgendadas()
        {
            if (ListaDeCurvasNaoFoiPaginada())
            {
                registrosExecucaoCurva = execucaoCurva.ObterRegistrosExecucaoCurvaPricingBIHF(dataHoraFim, SITUACAO_EXECUCAO_AGENDADA, SITUACAO_EXECUCAO_SOLICITADA_UPLOAD);

                if (registrosExecucaoCurva.Count > 15)
                {
                    registrosExecucaoCurva = registrosExecucaoCurva.OrderByDescending(x => x.DataHoraExecucaoCurva).ToList();
                }

                Logger.GravarLog(string.Format("Agendamentos obtidos: {0}", registrosExecucaoCurva.Count));
                Logger.GravarLog(string.Format("Pool de threads: {0}", Convert.ToInt32(mapaCofiguracoesGerador.Where(p => p.Chave == PARAMETRO_MAXIMOTHREADS).First().Valor)));
            }
        }

        private void ObterDataAtualDeExecucao()
        {
            dataHoraFim = System.DateTime.Now;
        }

        private void PaginarCurvas()
        {
            int maxThreadsPool = 0;
            int maxIOThreadsPool = 0;

            ThreadPool.GetAvailableThreads(out maxThreadsPool, out maxIOThreadsPool);

            if (registrosExecucaoCurva.Count > maxThreadsPool)
            {
                listaFoiPaginada = true;
                registrosExecucaoCurvaPaginada = registrosExecucaoCurva.Take(maxThreadsPool).ToList<RegistroExecucaoCurva>();
            }
            else
            {
                listaFoiPaginada = false;
                registrosExecucaoCurvaPaginada = registrosExecucaoCurva.ToList<RegistroExecucaoCurva>();
            }
        }

        private void ProcessarCurvas()
        {
            try
            {
                foreach (RegistroExecucaoCurva registroExecucaoCurva in registrosExecucaoCurvaPaginada)
                {
                    if (SeAgendadaRecentemente(registroExecucaoCurva) || SeNaoForHoraDeLimpeza())
                    {
                        Curva curvaEntidade = curva.ObterCurva(registroExecucaoCurva.CodigoCurva);

                        IGeradorDeCurva geradorCurva = FabricaDeGeradores.getInstance(curvaEntidade);
                        geradorCurva.Curva = curvaEntidade;
                        geradorCurva.RegistroExecucaoCurva = registroExecucaoCurva;
                        geradorCurva.MapaCofiguracoesGerador = mapaCofiguracoesGerador;
                        CriarThreadPorCurva(geradorCurva);
                    }
                    else
                    {
                        AlterarSituacaoDaExecucao(registroExecucaoCurva, SITUACAO_EXECUCAO_RETIRAR_AGENDAMENTO);
                        Logger.GravarLog(string.Format("Removida por exceder o tempo de tentativas de execuções. Código da curva: {0}. Data e hora execução: {1}.", registroExecucaoCurva.CodigoCurva, registroExecucaoCurva.DataHoraExecucaoCurva.ToString(MASCARA_DATA)));
                    }

                    registrosExecucaoCurva.Remove(registroExecucaoCurva);
                }
            }
            catch (Exception ex)
            {
                EnterpriseLibraryFactory.GetExceptionHandler().HandleException(ex, "ErrosGerador");
            }
            finally
            {
                AguardarExecucaoDasThreads();
            }
        }

        private bool SeNaoForHoraDeLimpeza()
        {
            return System.DateTime.Now.Hour != 1;
        }

        private void AguardarExecucaoDasThreads()
        {
            bool isThreadOK = true;

            while (isThreadOK)
            {
                int maxThreadsPool = 0;
                int maxIOThreadsPool = 0;

                ThreadPool.GetAvailableThreads(out maxThreadsPool, out maxIOThreadsPool);

                if (maxThreadsPool != 0)
                {
                    isThreadOK = false;
                }
                else
                {
                    System.Threading.Thread.Sleep(30000);
                }
            }
        }

        private bool SeAgendadaRecentemente(RegistroExecucaoCurva registroExecucaoCurva)
        {
            return System.DateTime.Now.Subtract(registroExecucaoCurva.DataHoraExecucaoCurva).TotalHours < Convert.ToInt32(mapaCofiguracoesGerador.Where(p => p.Chave == PARAMETRO_HORAS_TENTATIVA_GERACAO).First().Valor);
        }

        private void AlterarSituacaoDaExecucao(RegistroExecucaoCurva registroExecucaoCurva, int codigoSituacao)
        {
            registroExecucaoCurva.CodigoSituacao = codigoSituacao;
            execucaoCurva.AtualizarRegistroExecucaoCurva(registroExecucaoCurva);
        }

        private void CriarThreadPorCurva(IGeradorDeCurva geradorCurva)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(GerarCurva), geradorCurva);
        }

        public static void GerarCurva(object o)
        {
            IGeradorDeCurva geradorCurva = (IGeradorDeCurva)o;
            geradorCurva.Gerar();
        }

        private void AtualizarUltimaExecucao()
        {
            if (ListaDeCurvasNaoFoiPaginada())
            {
                configuracoes.AtualizarConfiguracao(nomeAplicativo, PARAMETRO_ULTIMAEXECUCAO, DateTime.Now.ToString("dd/MM/yyyy hh:mm:ss"));
            }
        }

        private void AguardarProximaExecucao()
        {
            if (OTempoDeExecucaoNaoUltrapassouAJanela() && ListaDeCurvasNaoFoiPaginada())
            {
                Logger.GravarLog(string.Format("Fim da execução: {0}", System.DateTime.Now.ToString(MASCARA_DATA)));

                System.Threading.Thread.Sleep(Convert.ToInt32((dataHoraFim.AddSeconds(janela) - System.DateTime.Now).TotalMilliseconds));
            }
        }

        private bool OTempoDeExecucaoNaoUltrapassouAJanela()
        {
            return dataHoraFim.AddSeconds(janela) > System.DateTime.Now;
        }

        private bool ListaDeCurvasNaoFoiPaginada()
        {
            return !listaFoiPaginada;
        }

        private void LimparObjetos()
        {
            curva = null;
            configuracoes = null;
            execucaoCurva = null;
            Logger.GravarLog("Fim");
        }

        #endregion
    }
}
